"""
Author: Jian SuiYue kaohx0214@yeah.net
Date: 2025-04-25 17:25:43
LastEditTime: 2025-05-15 17:21:27
FilePath: LibraryManagerStudio/database/logger_manager.py
"""
import datetime
import os
import re
import sys
import threading
from contextlib import contextmanager
from pathlib import Path
from typing import Dict, Any, Optional, List

from loguru import logger

from ..datamodel import Response

# delete default Loguru log handlers
logger.remove()

# logger level colors map
LEVEL_COLORS = {
    "TRACE": "<cyan>",
    "DEBUG": "<blue>",
    "INFO": "<green>",
    "SUCCESS": "<green>",
    "WARNING": "<yellow>",
    "ERROR": "<red>",
    "CRITICAL": "<red><bold>",
}

# logger format
LOG_FORMAT = ("<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{"
              "name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>")

# logger format for file
FILE_LOG_FORMAT = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}"

# linear logger
_thread_local = threading.local()

# database session getter
_db_session_getter = None
# logger root dir
_log_root_dir = None


def get_date_structured_path(base_dir: Path, now: datetime.datetime = None) -> Path:
    """
    Get the date structured path
    /base_dir/YYYY/MM/DD/
    
    Args:
        base_dir: base directory
        now: data time
    
    Returns:
        date structured path
    """
    if now is None:
        now = datetime.datetime.now()

    year_dir = base_dir / str(now.year)
    month_dir = year_dir / f"{now.month:02d}"
    day_dir = month_dir / f"{now.day:02d}"

    return day_dir


@contextmanager
def LoggerContext(uid: Optional[str] = None, **kwargs):
    """
    context manager for logger
    
    Args:
        uid: uid
        **kwargs: other context
    """
    # save old context value
    old_uid = getattr(_thread_local, "uid", None)
    old_context = getattr(_thread_local, "context", {}).copy()

    # set new context value
    _thread_local.uid = uid

    context = getattr(_thread_local, "context", {}).copy()
    context.update(kwargs)
    _thread_local.context = context

    try:
        yield
    finally:
        # restore old context value
        _thread_local.uid = old_uid
        _thread_local.context = old_context


def setup_loggers(log_dir: Optional[Path] = None, console_level: str = "INFO", file_level: str = "DEBUG"):
    """
    configure the loggers
    
    Args:
        log_dir: log root directory. default is None
        console_level: Console log level
        file_level: File log level
    """
    global _log_root_dir

    # Console logger handler
    logger.configure(
        handlers=[
            {
                "sink": sys.stdout,
                "format": LOG_FORMAT,
                "level": console_level,
                "colorize": True,
            }
        ]
    )

    # log root dir
    if log_dir:
        _log_root_dir = log_dir
        log_dir.mkdir(parents=True, exist_ok=True)

        log_path = log_dir / "{time:YYYY}/{time:MM}/{time:DD}/app_{time:HH-mm-ss}.log"

        # add file logger handler
        logger.add(
            sink=str(log_path),
            format=FILE_LOG_FORMAT,
            level=file_level,
            rotation="50 MB",  # file rotation interval
            compression="zip",  # file compression format
            retention="120 days",  # file retention period
            encoding="utf-8",
            enqueue=True,  # async log avoid blocking
        )

        app_logger.info(f"Already setting the log input to directory: {log_dir}")
        return Response(status=True, message="Setup logger successful")
    return Response(status=False, message="Logger Manager already exist in program")


def get_logs(
        year: Optional[int] = None,
        month: Optional[int] = None,
        day: Optional[int] = None,
        level: Optional[str] = None,
        search_text: Optional[str] = None,
        page: int = 1,
        page_size: int = 100
) -> Dict[str, Any]:
    """
    Get logs
    
    Args:
        year: year, if None, use current date
        month: month, if None, use current month
        day: day, if None, use current day
        level: log level
        search_text: search text
        page: page number
        page_size: page size
    
    Returns:
        dict, logs list
    """
    global _log_root_dir
    if not _log_root_dir:
        return {
            "logs": [],
            "total": 0,
            "page": page,
            "page_size": page_size,
            "total_pages": 0,
            "error": "log root directory not set"
        }

    # if year or month is None, use current date
    now = datetime.datetime.now()
    year = year or now.year
    month = month or now.month

    # built log path
    if day:
        # find specific day
        log_path = os.path.join(_log_root_dir, str(year), f"{month:02d}", f"{day:02d}")
        if not log_path.exists():
            return {
                "logs": [],
                "total": 0,
                "page": page,
                "page_size": page_size,
                "total_pages": 0
            }
        log_files = list(log_path.glob("*.log")) + list(log_path.glob("*.log.zip"))
    elif month:
        # find specific month
        month_path = _log_root_dir / str(year) / f"{month:02d}"
        if not month_path.exists():
            return {
                "logs": [],
                "total": 0,
                "page": page,
                "page_size": page_size,
                "total_pages": 0
            }
        log_files = []
        for day_dir in sorted(month_path.glob("*"), reverse=True):
            if day_dir.is_dir():
                log_files.extend(list(day_dir.glob("*.log")))
                log_files.extend(list(day_dir.glob("*.log.zip")))
    else:
        # find specific year
        year_path = _log_root_dir / str(year)
        if not year_path.exists():
            return {
                "logs": [],
                "total": 0,
                "page": page,
                "page_size": page_size,
                "total_pages": 0
            }
        log_files = []
        for month_dir in sorted(year_path.glob("*"), reverse=True):
            if month_dir.is_dir():
                for day_dir in sorted(month_dir.glob("*"), reverse=True):
                    if day_dir.is_dir():
                        log_files.extend(list(day_dir.glob("*.log")))
                        log_files.extend(list(day_dir.glob("*.log.zip")))

    # let log files sort by modify time
    log_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)

    # read logs
    logs = []
    total_logs = 0

    # use re to match the log format
    log_pattern = re.compile(
        r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| (\w+)\s+ \| (.+?) - (.+)"
    )

    # read logs
    for log_file in log_files:
        file_logs = []
        if log_file.suffix == '.zip':
            # TODO: unzip the log file
            continue

        try:
            with open(log_file, 'r', encoding='utf-8') as f:
                for line in f:
                    match = log_pattern.match(line.strip())
                    if match:
                        timestamp, log_level, source, message = match.groups()

                        # Skip logs that don't match the specified level
                        if level and level != log_level:
                            continue

                        # Skip logs that don't contain search text
                        if search_text and search_text.lower() not in message.lower():
                            continue

                        file_logs.append({
                            "timestamp": timestamp,
                            "level": log_level,
                            "source": source,
                            "message": message
                        })
        except Exception as e:
            # Log file read error, continue processing other files
            app_logger.error(f"read log file {log_file} failed: {str(e)}")
            continue

        # Add current file's logs to total log list
        logs.extend(file_logs)
        total_logs += len(file_logs)

    # Pagination processing
    start_idx = (page - 1) * page_size
    end_idx = start_idx + page_size
    logs_page = logs[start_idx:end_idx] if start_idx < len(logs) else []

    total_pages = (total_logs + page_size - 1) // page_size

    return {
        "logs": logs_page,
        "total": total_logs,
        "page": page,
        "page_size": page_size,
        "total_pages": total_pages
    }


def get_available_log_dates() -> Dict[str, List[Dict[str, Any]]]:
    """
    get available log dates
    
    Returns:
        dict, available log dates info sort by year and month
    """
    global _log_root_dir
    if not _log_root_dir or not _log_root_dir.exists():
        return {}

    result = {}

    # Scan all year directories
    for year_dir in sorted(_log_root_dir.glob("*"), reverse=True):
        if year_dir.is_dir() and year_dir.name.isdigit():
            year = year_dir.name
            result[year] = []

            # Scan all month directories
            for month_dir in sorted(year_dir.glob("*"), reverse=True):
                if month_dir.is_dir() and month_dir.name.isdigit():
                    month = month_dir.name
                    month_data = {
                        "month": month,
                        "days": []
                    }

                    # Scan all day directories
                    for day_dir in sorted(month_dir.glob("*"), reverse=True):
                        if day_dir.is_dir() and day_dir.name.isdigit():
                            day = day_dir.name
                            # Check for log files existence
                            log_files = list(day_dir.glob("*.log")) + list(day_dir.glob("*.log.zip"))
                            if log_files:
                                month_data["days"].append(day)

                    if month_data["days"]:
                        result[year].append(month_data)

    return result


def get_user_logger(name: str):
    """
    get user logger
    
    Args:
        name: log name
    
    Returns:
        logger instance
    """
    return logger.bind(name=name)


def get_system_logger(name: str):
    """
    get system logger
    
    Args:
        name: log name
    
    Returns:
        logger instance
    """
    return logger.bind(name=f"SYSTEM:{name}")


# Default logger
app_logger = get_system_logger("app")
